//main.cpp
/**************************************************************************
module: waf
file(s): cross_list.h, sparse_matrix.h, waf.h, waf_text.h, main.cpp
function: provide toolset for text Word Activation Force (or WAF) analysis.

	This module provides an shell interface of waf analysis. They use data
	structures and call functions of other modules.

author: Yongtian Zhang, yongtianzhang@gmail.com
finish-time: 2011.04.21
last-modified: 2011.04.24

license:
	This code is not under any license, anyone can get it, use it, modified it, redistribute it without notice me. 
	I don't think there is no bug in it, so before you use it, please check and test carefully, I don't promise its quality. ^_^
	If you find any bug, please report it to me, I'll be very grateful to you. Discussion is also welcome.

**************************************************************************/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <deque>
#include <cctype>
#include <algorithm>
#include <iterator>
#include <utility>
#include <sstream>
#include <ctime>
#include <set>

#include <cstdlib>
#include <memory>
#include <limits>

#include "waf.h"
#include "waf_text.h"
#include "org_gephi_statistics_spi_CRSMetrics.h"

using namespace std;

//stores node information of cross list
template <typename T>
struct node_info
{
	waf::termid_type row;
	waf::termid_type col;
	T value;
	node_info(waf::termid_type r=0,waf::termid_type c=0, T v=T()):
		row(r),col(c),value(v)
	{
	}
	static bool value_greater(const node_info& lhs, const node_info& rhs)
	{
		if (lhs.value>rhs.value || rhs.value>lhs.value)
			return lhs.value > rhs.value;
		if (lhs.row<rhs.row || rhs.row<lhs.row)
			return lhs.row < rhs.row;
		return lhs.col < rhs.col;
	}
};

template <typename T>
node_info<T> make_node_info(
		waf::termid_type r=0,waf::termid_type c=0, T v=T())
{
	node_info<T> ti;
	ti.row = r; ti.col = c; ti.value = v;
	return ti;
}

int run_task(int argc, char* argv[]);

template <typename InputIterator>
int run_ti(InputIterator arg_beg, InputIterator arg_end);

template <typename InputIterator>
int run_tf(InputIterator arg_beg, InputIterator arg_end);

template <typename InputIterator>
int run_co(InputIterator arg_beg, InputIterator arg_end);

template <typename InputIterator>
int run_waf(InputIterator arg_beg, InputIterator arg_end);

template <typename InputIterator>
int run_a(InputIterator arg_beg, InputIterator arg_end);

template <typename InputIterator>
int run_info(InputIterator arg_beg, InputIterator arg_end);

int print_help_info();

template <typename InputIterator>
void init_option_table(
		InputIterator opt_beg, InputIterator opt_end,
		map<string,vector<string> >& option_table);

template <typename InputIterator>
void fill_option_table(
		InputIterator arg_beg, InputIterator arg_end,
		map<string,vector<string> >& option_table);

bool resolve_unknown_otions(
		const vector<string>& args, const string& func_hint);

bool resolve_file_input_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, istream*& is_ptr, ifstream& fin);

bool resolve_files_input_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, int limit,
		bool& exist, vector<string>& file_vec);

bool resolve_file_output_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, ostream*& os_ptr, ofstream& fout);

bool resolve_term_info_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, bool& exist, ifstream& fs);

bool resolve_multi_arg_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, int limit,
		bool& exist, vector<string>& arg_vec);

bool resolve_single_arg_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, bool& exist, string& arg);

bool resolve_none_arg_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, bool& exist);

template <typename T, size_t N>
bool resolve_arg_in_table(
		const string& arg, const T (&table)[N],
		const string& opt_hint, const string& func_hint);

template <typename T1, typename T2>
ostream& operator << (ostream& os, const pair<T1,T2>& p);

template <typename T1, typename T2>
istream& operator >> (istream& is, pair<T1,T2>& p);

template <typename T>
ostream& operator << (ostream& os, const node_info<T>& ni);

template <typename ToType, typename FromType>
ToType cast(const FromType& from);

//parameter: Iterator, should be cross_list<T>::row_iterator/col_iterator/iterator
//parameter: OutputIterator, should reference to node_info<T> object
template <typename Iterator, typename OutputIterator>
void extract_node_info(Iterator first, Iterator last, OutputIterator out);

template <typename T>
void analyze_outlink(const cross_list<T>& mat,
		deque<deque<node_info<T> > >& node_vecs);

template <typename T>
void analyze_inlink(const cross_list<T>& mat,
		deque<deque<node_info<T> > >& node_vecs);

template <typename T>
void analyze_pair(const cross_list<T>& mat,
		deque<node_info<T> >& node_vec);

template <typename T>
bool analyze_matrix(const cross_list<T>& mat, const string& type,
		const string& func_hint, waf::size_type node_limit,
		deque<deque<node_info<T> > >& node_vecs);

template <typename T, typename Term>
bool analyze_process(
		istream& is, bool use_term_info,term_info<Term>& tinfo,
		const string& analyze_type, const string& func_hint,
		waf::size_type node_limit, ostream& os);


JNIEXPORT jint JNICALL Java_org_gephi_statistics_spi_CRSMetrics_intMethod(JNIEnv *env, jobject obj, jint num){
	return num * num;
}

JNIEXPORT jint JNICALL Java_org_gephi_statistics_spi_CRSMetrics_calculateCoMatrix
    (JNIEnv *env, jobject obj, jstring j_term_info, jstring j_termid_list, jstring j_co_matrix, jstring j_win_size){
    char* term_info = env->GetStringUTFChars(j_term_info, 0);
    char* termid_list = env->GetStringUTFChars(j_termid_list, 0);
    char* co_matrix = env->GetStringUTFChars(j_co_matrix, 0);
    char* win_size = env->GetStringUTFChars(j_win_size, 0);
    char* argv[10] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'};
    argv[0] = "waf";
    argv[1] = "-co";
    argv[2] = "-f";
    argv[3] = termid_list;
    argv[4] = "-t";
    argv[5] = term_info;
    argv[6] = "-win";
    argv[7] = win_size;
    argv[8] = "-o";
    argv[9] = co_matrix;
    cout << argv[0] << " "
        << argv[1] << " "
        << argv[2] << " "
        << argv[3] << " "
        << argv[4] << " "
        << argv[5] << " "
        << argv[6] << " "
        << argv[7] << " "
        << argv[8] << " "
        << argv[9] << " " << endl;
    int rtn = main(10, argv);
    env->ReleaseStringUTFChars(j_term_info, term_info);
    env->ReleaseStringUTFChars(j_termid_list, termid_list);
    env->ReleaseStringUTFChars(j_co_matrix, co_matrix);
    env->ReleaseStringUTFChars(j_win_size, win_size);
    return rtn;
}

JNIEXPORT jint JNICALL Java_org_gephi_statistics_spi_CRSMetrics_calculateWafMatrix
    (JNIEnv *env, jobject obj, jstring j_co_matrix, jstring j_term_info, jstring j_waf_matrix, jstring j_param){
    char* co_matrix = env->GetStringUTFChars(j_co_matrix, 0);
    char* term_info = env->GetStringUTFChars(j_term_info, 0);
    char* waf_matrix = env->GetStringUTFChars(j_waf_matrix, 0);
    char* param = env->GetStringUTFChars(j_param, 0);
    char* argv[10] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'};
    argv[0] = "waf";
    argv[1] = "-waf";
    argv[2] = "-f";
    argv[3] = co_matrix;
    argv[4] = "-t";
    argv[5] = term_info;
    argv[6] = "-p";
    argv[7] = param;
    argv[8] = "-o";
    argv[9] = waf_matrix;
    cout << argv[0] << " "
        << argv[1] << " "
        << argv[2] << " "
        << argv[3] << " "
        << argv[4] << " "
        << argv[5] << " "
        << argv[6] << " "
        << argv[7] << " "
        << argv[8] << " "
        << argv[9] << " " << endl;

    int rtn = main(10, argv);
    env->ReleaseStringUTFChars(j_co_matrix, co_matrix);
    env->ReleaseStringUTFChars(j_term_info, term_info);
    env->ReleaseStringUTFChars(j_waf_matrix, waf_matrix);
    env->ReleaseStringUTFChars(j_param, param);
    return rtn;
}

JNIEXPORT jint JNICALL Java_org_gephi_statistics_spi_CRSMetrics_calculateAffinityMatrix
    (JNIEnv *env, jobject obj, jstring j_waf_matrix, jstring j_term_info, jstring j_affinity_matrix, jstring j_param){
    char* waf_matrix = env->GetStringUTFChars(j_waf_matrix, 0);
    char* term_info = env->GetStringUTFChars(j_term_info, 0);
    char* affinity_matrix = env->GetStringUTFChars(j_affinity_matrix, 0);
    char* param = env->GetStringUTFChars(j_param, 0);
    char* argv[10] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'};
    argv[0] = "waf";
    argv[1] = "-a";
    argv[2] = "-f";
    argv[3] = waf_matrix;
    argv[4] = "-t";
    argv[5] = term_info;
    argv[6] = "-p";
    argv[7] = param;
    argv[8] = "-o";
    argv[9] = affinity_matrix;
    cout << argv[0] << " "
        << argv[1] << " "
        << argv[2] << " "
        << argv[3] << " "
        << argv[4] << " "
        << argv[5] << " "
        << argv[6] << " "
        << argv[7] << " "
        << argv[8] << " "
        << argv[9] << " " << endl;

    int rtn = main(10, argv);
    env->ReleaseStringUTFChars(j_waf_matrix, waf_matrix);
    env->ReleaseStringUTFChars(j_term_info, term_info);
    env->ReleaseStringUTFChars(j_affinity_matrix, affinity_matrix);
    env->ReleaseStringUTFChars(j_param, param);
    return rtn;

}

JNIEXPORT jint JNICALL Java_org_gephi_statistics_spi_CRSMetrics_calculateTermInfo
    (JNIEnv *env, jobject obj, jstring j_termid_list, jstring j_term_info){
    char* term_info = env->GetStringUTFChars(j_term_info, 0);
    char* termid_list = env->GetStringUTFChars(j_termid_list, 0);
    char* argv[6] = {'\0', '\0', '\0', '\0', '\0', '\0'};
    argv[0] = "waf";
    argv[1] = "-tf";
    argv[2] = "-f";
    argv[3] = termid_list;
    argv[4] = "-o";
    argv[5] = term_info;
    cout << argv[0] << " "
        << argv[1] << " "
        << argv[2] << " "
        << argv[3] << " "
        << argv[4] << " "
        << argv[5] << " " << endl;
    int rtn = main(6, argv);
    env->ReleaseStringUTFChars(j_term_info, term_info);
    env->ReleaseStringUTFChars(j_termid_list, termid_list);
    return rtn;
}

int main(int argc, char* argv[])
{
	try
	{
		return run_task(argc,argv);
	}
	catch (const exception& e)
	{
		cerr << "error (exception): " << e.what() << endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

int run_task(int argc, char* argv[])
{
	//check command line arguments
	if (argc<2)
		return print_help_info();

	//dispatch task
	string task_type=argv[1];
	char** arg_beg=argv+2;
	char** arg_end=argv+argc;
	if (task_type=="-ti")
		return run_ti(arg_beg,arg_end);
	else if (task_type=="-tf")
		return run_tf(arg_beg,arg_end);
	else if (task_type=="-co")					//calculate co-occurrence
		return run_co(arg_beg,arg_end);
	else if (task_type=="-waf")					//calculate waf matrix
		return run_waf(arg_beg,arg_end);
	else if (task_type=="-a")					//calculate affinity matrix
		return run_a(arg_beg,arg_end);
	else if (task_type=="-info")				//analyze information
		return run_info(arg_beg,arg_end);
	else if (task_type=="-h" || task_type=="--help")//print help information
		return print_help_info();
	else
	{
		cerr << "error task type: " << task_type << endl;
		return EXIT_FAILURE;
	}
}

template <typename InputIterator>
int run_ti(InputIterator arg_beg, InputIterator arg_end)
{
	//init option table
	map<string,vector<string> > option_table;
	string options[]={"-f","-t","-o","-delim","-to"};
	init_option_table(options,options+sizeof(options)/sizeof(string),option_table);
	fill_option_table(arg_beg,arg_end,option_table);
	string func_hint("term-to-termid: ");
	if (!resolve_unknown_otions(option_table[""],func_hint))
		return EXIT_FAILURE;

	//input stream setting
	ifstream fin;
	istream* is_ptr=NULL;
	if (!resolve_file_input_option(option_table["-f"],"-f",func_hint,is_ptr,fin))
		return EXIT_FAILURE;

	//input term info setting
	ifstream fti;
	bool use_term_info=false;
	if (!resolve_term_info_option(option_table["-t"],"-t",func_hint,use_term_info,fti))
		return EXIT_FAILURE;

	//output term info setting
	bool gen_term_info=false;
	string tinfo_file;
	if (!resolve_single_arg_option(option_table["-to"],"-to",func_hint,gen_term_info,tinfo_file))
		return EXIT_FAILURE;

	//output stream setting
	ofstream fout;
	ostream* os_ptr=NULL;
	if (!resolve_file_output_option(option_table["-o"],"-o",func_hint,os_ptr,fout))
		return EXIT_FAILURE;

	//delimiters setting
	vector<string> delim_vec;
	bool have_delim_opt=false;
	if (!resolve_multi_arg_option(option_table["-delim"],"-delim",func_hint,-1,have_delim_opt,delim_vec))
		return EXIT_FAILURE;
	set<string> delim_set;						//build delim term set
	copy(delim_vec.begin(),delim_vec.end(),inserter(delim_set,delim_set.end()));

	//work
	term_info<string> tinfo;
	if (use_term_info)							//read input term info
	{
		fti >> tinfo;
		fti.close();							//close term info file
	}
	string text;
	while (getline(*is_ptr,text))				//read one line
	{
		stringstream ss(text);
		for (string term; ss>>term;)			//for each term
		{
			if (delim_set.count(term))			//check delim term
			{
				*os_ptr << static_cast<long>(waf::delim_termid) << " ";
				continue;
			}
			*os_ptr << tinfo.term_freq_inc(term) << " ";//increase term frequency
		}
		if (is_ptr->good())						//if istream good, output '\n'
			*os_ptr << '\n';
	}
	if (gen_term_info)							//output term info file
	{
		ofstream ftio(tinfo_file.c_str());
		ftio << tinfo;
	}
	return EXIT_SUCCESS;
}

template <typename InputIterator>
int run_tf(InputIterator arg_beg, InputIterator arg_end)
{
	//init option table
	map<string,vector<string> > option_table;
	string options[]={"-m","-f","-t","-o"};
	init_option_table(options,options+sizeof(options)/sizeof(string),option_table);
	fill_option_table(arg_beg,arg_end,option_table);
	string func_hint("term-frequency: ");
	if (!resolve_unknown_otions(option_table[""],func_hint))
		return EXIT_FAILURE;

	//data source setting
	vector<string> id_file_vec, list_file_vec;
	bool use_id_file_opt=false, use_list_file_opt=false;
	if (!resolve_files_input_option(option_table["-f"],"-f",func_hint,-1,use_id_file_opt,id_file_vec))
		return EXIT_FAILURE;
	if (!resolve_files_input_option(option_table["-m"],"-m",func_hint,-1,use_list_file_opt,list_file_vec))
		return EXIT_FAILURE;
	if (id_file_vec.empty() && list_file_vec.empty())
	{
		cerr << func_hint << "no data source" << endl;
		return EXIT_FAILURE;
	}

	//term info setting
	ifstream fti;
	bool use_term_info=false;
	if (!resolve_term_info_option(option_table["-t"],"-t",func_hint,use_term_info,fti))
		return EXIT_FAILURE;

	//output stream setting
	ofstream fout;
	ostream* os_ptr=NULL;
	if (!resolve_file_output_option(option_table["-o"],"-o",func_hint,os_ptr,fout))
		return EXIT_FAILURE;

	//work
	term_info<string> tinfo, tinfo_out;
	if (use_term_info)
		fti >> tinfo;
	tinfo_out = tinfo;
	for (vector<string>::size_type i=0; i<id_file_vec.size(); ++i)//scan id file
	{
		if (use_term_info)
			scan_termid_file(id_file_vec[i],tinfo.freq_identifier(),tinfo_out);
		else
			scan_termid_file(id_file_vec[i],tinfo_out);
	}
	for (vector<string>::size_type i=0; i<list_file_vec.size(); ++i)//scan id file list
	{
		ifstream fin(list_file_vec[i].c_str());
		string id_file;
		while (getline(fin,id_file))
		{
			if (use_term_info)
				scan_termid_file(id_file,tinfo.freq_identifier(),tinfo_out);
			else
				scan_termid_file(id_file,tinfo_out);
		}
	}
	*os_ptr << tinfo_out;
	return EXIT_SUCCESS;
}

template <typename InputIterator>
int run_co(InputIterator arg_beg, InputIterator arg_end)
{
	//init option table
	map<string,vector<string> > option_table;
	string options[]={"-win","-o","-m","-f","-t"};
	init_option_table(options,options+sizeof(options)/sizeof(string),option_table);
	fill_option_table(arg_beg,arg_end,option_table);
	string func_hint("co-occurrence: ");
	if (!resolve_unknown_otions(option_table[""],func_hint))
		return EXIT_FAILURE;

	//data source setting
	vector<string> id_file_vec, list_file_vec;
	bool use_id_file_opt=false, use_list_file_opt=false;
	if (!resolve_files_input_option(option_table["-f"],"-f",func_hint,-1,use_id_file_opt,id_file_vec))
		return EXIT_FAILURE;
	if (!resolve_files_input_option(option_table["-m"],"-m",func_hint,-1,use_list_file_opt,list_file_vec))
		return EXIT_FAILURE;
	if (id_file_vec.empty() && list_file_vec.empty())
	{
		cerr << func_hint << "no data source" << endl;
		return EXIT_FAILURE;
	}

	//co-occurrence window size
	bool have_win_opt=false;
	string win_opt;
	if (!resolve_single_arg_option(option_table["-win"],"-win",func_hint,have_win_opt,win_opt))
		return EXIT_FAILURE;
	if (!have_win_opt)
	{
		cerr << func_hint << "missing option -win" << endl;
		return EXIT_FAILURE;
	}
	waf::size_type co_win=cast<waf::size_type>(win_opt);

	//output stream setting
	ofstream fout;
	ostream* os_ptr=NULL;
	if (!resolve_file_output_option(option_table["-o"],"-o",func_hint,os_ptr,fout))
		return EXIT_FAILURE;

	//term info setting
	term_info<string> tinfo;
	ifstream fti;
	bool use_term_info=true;
	if (!resolve_term_info_option(option_table["-t"],"-t",func_hint,use_term_info,fti))
		return EXIT_FAILURE;

	//work
	typedef pair<waf::distance_type,waf::size_type> co_type;
	sparse_matrix<co_type> co_mat;
	co_mat.sparse(50,50);
	if (use_term_info)
		fti >> tinfo;
	for (vector<string>::size_type i=0; i<id_file_vec.size(); ++i)//scan id file
	{
		if (use_term_info)
			scan_termid_file(id_file_vec[i],tinfo.freq_identifier(),co_win,co_mat);
		else
			scan_termid_file(id_file_vec[i],co_win,co_mat);
	}
	for (vector<string>::size_type i=0; i<list_file_vec.size(); ++i)//scan id file list
	{
		ifstream fin(list_file_vec[i].c_str());
		string id_file;
		while (getline(fin,id_file))
		{
			if (use_term_info)
				scan_termid_file(id_file,tinfo.freq_identifier(),co_win,co_mat);
			else
				scan_termid_file(id_file,co_win,co_mat);
		}
	}
	waf::mean_distance(co_mat.begin(),co_mat.end());
	*os_ptr << dynamic_cast<const cross_list<co_type>&>(co_mat);
	return EXIT_SUCCESS;
}

template <typename InputIterator>
int run_waf(InputIterator arg_beg, InputIterator arg_end)
{
	//init option table
	map<string,vector<string> > option_table;
	string options[]={"-p","-f","-t","-o"};
	init_option_table(options,options+sizeof(options)/sizeof(string),option_table);
	fill_option_table(arg_beg,arg_end,option_table);
	string func_hint("word-activation-force: ");
	if (!resolve_unknown_otions(option_table[""],func_hint))
		return EXIT_FAILURE;

	//precision setting
	bool have_prec_opt=false;
	string prec_opt;
	if (!resolve_single_arg_option(option_table["-p"],"-p",func_hint,have_prec_opt,prec_opt))
		return EXIT_FAILURE;
	waf::force_type prec=have_prec_opt ? cast<waf::force_type>(prec_opt) : 0;

	//input stream setting
	ifstream fin;
	istream* is_ptr=NULL;
	if (!resolve_file_input_option(option_table["-f"],"-f",func_hint,is_ptr,fin))
		return EXIT_FAILURE;

	//term information input setting
	term_info<string> tinfo;
	bool have_term_info=false;
	ifstream fti;
	if (!resolve_term_info_option(option_table["-t"],"-t",func_hint,have_term_info,fti))
		return EXIT_FAILURE;
	if (!have_term_info)
	{
		cerr << func_hint << "missing option -t" << endl;
		return EXIT_FAILURE;
	}

	//output stream setting
	ofstream fout;
	ostream* os_ptr=NULL;
	if (!resolve_file_output_option(option_table["-o"],"-o",func_hint,os_ptr,fout))
		return EXIT_FAILURE;

	//work
	typedef pair<waf::distance_type,waf::size_type> co_type;
	cross_list<co_type> co_mat;
	cross_list<waf::force_type> waf_mat;
	*is_ptr >> co_mat;
	waf_mat.resize(co_mat.row_size(),co_mat.col_size());
	fti >> tinfo;
	waf::word_activation_force(co_mat,tinfo.freq_identifier(),
			tinfo.freq_identifier(),prec,waf_mat);
	*os_ptr << waf_mat;
	return EXIT_SUCCESS;
}

template <typename InputIterator>
int run_a(InputIterator arg_beg, InputIterator arg_end)
{
	//init option table
	map<string,vector<string> > option_table;
	string options[]={"-p","-f","-o","-t","-vec"};
	init_option_table(options,options+sizeof(options)/sizeof(string),option_table);
	fill_option_table(arg_beg,arg_end,option_table);
	string func_hint("affinity-measure: ");
	if (!resolve_unknown_otions(option_table[""],func_hint))
		return EXIT_FAILURE;

	//input stream setting
	ifstream fin,fin2;
	istream* is_ptr=NULL, *is_ptr2=NULL;
	bool have_file_opt=false;
	vector<string> file_vec;
	if (!resolve_files_input_option(option_table["-f"],"-f",func_hint,2,have_file_opt,file_vec))
		return EXIT_FAILURE;
	if (!have_file_opt)
		is_ptr = &cin;
	else
	{
		fin.open(file_vec[0].c_str());
		is_ptr = &fin;
		if (file_vec.size()==2)
		{
			fin2.open(file_vec[1].c_str());
			is_ptr2 = &fin2;
		}
	}

	//output stream setting
	ofstream fout;
	ostream* os_ptr=NULL;
	if (!resolve_file_output_option(option_table["-o"],"-o",func_hint,os_ptr,fout))
		return EXIT_FAILURE;

	//term info setting
	term_info<string> tinfo;
	ifstream fti;
	bool use_term_info=true;
	if (!resolve_term_info_option(option_table["-t"],"-t",func_hint,use_term_info,fti))
		return EXIT_FAILURE;

	//vector mode setting
	bool to_a_vec=false;
	if (!resolve_none_arg_option(option_table["-vec"],"-vec",func_hint,to_a_vec))
		return EXIT_FAILURE;
	if (to_a_vec && NULL==is_ptr2)
	{
		cerr << func_hint << "too few input files for option -vec" << endl;
		return EXIT_FAILURE;
	}

	//precision setting
	bool have_prec_opt=false;
	string prec_opt;
	if (!resolve_single_arg_option(option_table["-p"],"-p",func_hint,have_prec_opt,prec_opt))
		return EXIT_FAILURE;
	waf::affinity_type prec=have_prec_opt ? cast<waf::affinity_type>(prec_opt) : 0;
	if (to_a_vec && have_prec_opt)
		cerr << func_hint << "warning: ignore option -p in vector output mode" << endl;

	//work
	cross_list<waf::force_type> waf_mat,waf_mat2;
	cross_list<waf::affinity_type> a_mat;
	deque<waf::affinity_type> a_vec;
	if (use_term_info)
		fti >> tinfo;
	*is_ptr >> waf_mat;
	a_mat.resize(waf_mat.row_size(),waf_mat.col_size());
	if (NULL==is_ptr2)
	{
		if (use_term_info)
			waf::affinity_measure(waf_mat,tinfo.freq_identifier(),prec,a_mat);
		else
			waf::affinity_measure(waf_mat,prec,a_mat);
	}
	else //two waf matrices
	{
		*is_ptr2 >> waf_mat2;
		if (waf_mat2.row_size()!=waf_mat.row_size() || 
				waf_mat2.col_size()!=waf_mat.col_size())
		{
			cerr << func_hint << "two waf matrices have different dimension" << endl;
			return EXIT_FAILURE;
		}
		if (!to_a_vec)
		{
			if (use_term_info)
				waf::affinity_measure(waf_mat,waf_mat2,tinfo.freq_identifier(),prec,a_mat);
			else
				waf::affinity_measure(waf_mat,waf_mat2,prec,a_mat);
		}
		else
		{
			a_vec.resize(waf_mat.row_size());
			if (use_term_info)
				waf::affinity_measure_vector(waf_mat,waf_mat2,tinfo.freq_identifier(),a_vec.begin());
			else
				waf::affinity_measure_vector(waf_mat,waf_mat2,a_vec.begin());
		}
	}
	if (!to_a_vec)
		*os_ptr << a_mat;
	else
		copy(a_vec.begin(),a_vec.end(),ostream_iterator<waf::affinity_type>(*os_ptr,"\n"));
	return EXIT_SUCCESS;
}

template <typename InputIterator>
int run_info(InputIterator arg_beg, InputIterator arg_end)
{
	//init option table
	map<string,vector<string> > option_table;
	string options[]={"-f","-o","-t","-n","-mat","-type"};
	init_option_table(options,options+sizeof(options)/sizeof(string),option_table);
	fill_option_table(arg_beg,arg_end,option_table);
	string func_hint("information-analysis: ");
	if (!resolve_unknown_otions(option_table[""],func_hint))
		return EXIT_FAILURE;

	//input stream setting
	ifstream fin;
	istream* is_ptr=NULL;
	if (!resolve_file_input_option(option_table["-f"],"-f",func_hint,is_ptr,fin))
		return EXIT_FAILURE;

	//term info setting
	ifstream fti;
	bool use_term_info=true;
	if (!resolve_term_info_option(option_table["-t"],"-t",func_hint,use_term_info,fti))
		return EXIT_FAILURE;

	//output stream setting
	ofstream fout;
	ostream* os_ptr=NULL;
	if (!resolve_file_output_option(option_table["-o"],"-o",func_hint,os_ptr,fout))
		return EXIT_FAILURE;

	//matrix type
	string mat_opts[]={"waf","affinity"};
	string mat_type(mat_opts[0]);				//default to first of list
	bool have_mat_opt=false;
	if (!resolve_single_arg_option(option_table["-mat"],"-mat",func_hint,have_mat_opt,mat_type))
		return EXIT_FAILURE;
	if (have_mat_opt && !resolve_arg_in_table(mat_type,mat_opts,"-mat",func_hint))
		return EXIT_FAILURE;

	//analysis type
	string type_opts[]={"pair","in-link","out-link"};
	string analyze_type(type_opts[0]);			//default to first of list
	bool have_type_opt=false;
	if (!resolve_single_arg_option(option_table["-type"],"-type",func_hint,have_type_opt,analyze_type))
		return EXIT_FAILURE;
	if (have_type_opt && !resolve_arg_in_table(analyze_type,type_opts,"-type",func_hint))
		return EXIT_FAILURE;

	//presenting limit size
	waf::size_type node_limit=numeric_limits<waf::size_type>::max();//default to non-limit size
	bool have_limit_opt=false;
	string limit_opt;
	if (!resolve_single_arg_option(option_table["-n"],"-n",func_hint,have_limit_opt,limit_opt))
		return EXIT_FAILURE;
	if (have_limit_opt)
		node_limit = cast<waf::size_type>(limit_opt);

	//work
	term_info<string> tinfo;
	if (use_term_info)
		fti >> tinfo;

	if (mat_type=="waf")
	{
		if (!analyze_process<waf::force_type>(*is_ptr,use_term_info,tinfo,
					analyze_type,func_hint,node_limit,*os_ptr))
			return EXIT_FAILURE;
	}
	else if (mat_type=="affinity")
	{
		if (!analyze_process<waf::affinity_type>(*is_ptr,use_term_info,tinfo,
					analyze_type,func_hint,node_limit,*os_ptr))
			return EXIT_FAILURE;
	}
	else
	{
		cerr << func_hint << "fail to read matrix" << endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

int print_help_info()
{
	cout << "waf <task type> <task options>                      " << endl;
	cout << "    <task type>:                                    " << endl;
	cout << "        -a: calculate affinity measure              " << endl;
	cout << "        -co: calculate co-occurrence matrix         " << endl;
	cout << "        -h, --help: print help information          " << endl;
	cout << "        -info: analyze matrix information           " << endl;
	cout << "        -waf: calculate waf matrix                  " << endl;
	cout << "        -tf: calculate term frequency               " << endl;
	cout << "        -ti: convert terms to termid's              " << endl;
	cout << "                                                    " << endl;
	cout << "    -a <task options>:                              " << endl;
	cout << "        -f: set input waf matrix file(s), if not    " << endl;
	cout << "            set, read one waf matrix from stdin, if " << endl;
	cout << "            set one file, calculate affinity measure" << endl;
	cout << "            of this waf matrix, if set two files,   " << endl;
	cout << "            calculate affinity measure of the two   " << endl;
	cout << "            waf matrix, report error otherwise      " << endl;
	cout << "        -o: set output affinity matrix file, if not " << endl;
	cout << "            set, write affinity matrix to stdout    " << endl;
	cout << "        -p: set precision, affinity measure less    " << endl;
	cout << "            than it will not be stored. if not set, " << endl;
	cout << "            precision default to 0                  " << endl;
	cout << "        -t: set input term information file, term   " << endl;
	cout << "            with zero frequency will not calculate  " << endl;
	cout << "            affinity measure, if not set, all terms " << endl;
	cout << "            will calculate affinity measure         " << endl;
	cout << "        -vec: if set, calculate affinity measure of " << endl;
	cout << "            each pair of same termid's in separate  " << endl;
	cout << "            waf matrices. if not set calculate      " << endl;
	cout << "            affinity matrix of input waf matrix     " << endl;
	cout << "            (matrices)                              " << endl;
	cout << "                                                    " << endl;
	cout << "    -info <task options>:                           " << endl;
	cout << "        -f: set input matrix file, if not set, read " << endl;
	cout << "            matrix from stdin                       " << endl;
	cout << "        -mat: set matrix type:                      " << endl;
	cout << "            'affinity': treat input as affinity     " << endl;
	cout << "                matrix                              " << endl;
	cout << "            'waf': treat input as waf matrix        " << endl;
	cout << "        -n: set present number 'num' of results. if " << endl;
	cout << "            '-type' is 'pair', present highest num  " << endl;
	cout << "            node values to output, if '-type' is    " << endl;
	cout << "            'in-link', present highest num node     " << endl;
	cout << "            values of each column to output, if     " << endl;
	cout << "            '-type' is 'out-link', present highest  " << endl;
	cout << "            num node values of each row to output,  " << endl;
	cout << "            if not set, present all node values to  " << endl;
	cout << "            output.                                 " << endl;
	cout << "        -o: set info output file, if not set, write " << endl;
	cout << "            info to stdout                          " << endl;
	cout << "        -t: set input term information file, term   " << endl;
	cout << "            with zero frequency will not analyze    " << endl;
	cout << "            information, and output will be <term,  " << endl;
	cout << "            term, value> tuple. if not set, all     " << endl;
	cout << "            nodes in matrix will analyze information" << endl;
	cout << "            and output will be <termid,termid,value>" << endl;
	cout << "            tuple                                   " << endl;
	cout << "        -type: set analyze type:                    " << endl;
	cout << "            'in-link': sort nodes of each column in " << endl;
	cout << "                desc order                          " << endl;
	cout << "            'out-link': sort nodes of each row in   " << endl;
	cout << "                desc order                          " << endl;
	cout << "            'pair'(default): sort all nodes in      " << endl;
	cout << "                matrix in desc order                " << endl;
	cout << "                                                    " << endl;
	cout << "    -co <task options>:                             " << endl;
	cout << "        -f: set input termid file, delimited with   " << endl;
	cout << "            space or '\\n', more than one files is  " << endl;
	cout << "            also ok                                 " << endl;
	cout << "        -m: set input list_file, which list each    " << endl;
	cout << "            termid file in a line, more than one    " << endl;
	cout << "            list_file(s) is also ok                 " << endl;
	cout << "        -o: set output co-occurrence matrix file, if" << endl;
	cout << "            not set, write co-occurrence matrix to  " << endl;
	cout << "            stdout                                  " << endl;
	cout << "        -t: set input term information file, term   " << endl;
	cout << "            with zero frequency will not calculate  " << endl;
	cout << "            co-occurrence, if not set, all terms    " << endl;
	cout << "            will calculate co-occurrence            " << endl;
	cout << "        -win: set co-occurrence window size co_win, " << endl;
	cout << "            then max co-occurrence pair distance is " << endl;
	cout << "            co_win-1                                " << endl;
	cout << "                                                    " << endl;
	cout << "    -tf <task options>:                             " << endl;
	cout << "        -f: set input termid file, delimited with   " << endl;
	cout << "            space or '\\n', more than one files is  " << endl;
	cout << "            also ok                                 " << endl;
	cout << "        -m: set input list_file, which list each    " << endl;
	cout << "            termid file in a line, more than one    " << endl;
	cout << "            list_file(s) is also ok                 " << endl;
	cout << "        -o: set output term information file, if not" << endl;
	cout << "            set, write term information to stdout   " << endl;
	cout << "        -t: set input term information file, term   " << endl;
	cout << "            with zero frequency will not calculate  " << endl;
	cout << "            term information, if not set, all terms " << endl;
	cout << "            will calculate term information         " << endl;
	cout << "                                                    " << endl;
	cout << "    -ti <task options>:                             " << endl;
	cout << "        -delim: set delimiter term(s), which will be" << endl;
	cout << "            treated as end of one term list unit    " << endl;
	cout << "        -f: set input term list file, delimited with" << endl;
	cout << "            space or '\\n'. if not set, read term   " << endl;
	cout << "            list from stdin                         " << endl;
	cout << "        -t: set input term information file, term   " << endl;
	cout << "            information of the imput term list file " << endl;
	cout << "            will be appended to the input term      " << endl;
	cout << "            term information                        " << endl;
	cout << "        -to: set output term information file, term " << endl;
	cout << "            information of -f argument and -t       " << endl;
	cout << "            argument add up will be output into it  " << endl;
	cout << "        -o: set output termid list file, if not     " << endl;
	cout << "            set, write termid list to stdout        " << endl;
	cout << "                                                    " << endl;
	cout << "    -waf <task options>:                            " << endl;
	cout << "        -f: set input co-occurrence matrix file, if " << endl;
	cout << "            not set, read co-occurrence matrix from " << endl;
	cout << "            stdin                                   " << endl;
	cout << "        -o: set output waf matrix file, if not set, " << endl;
	cout << "            write waf matrix to stdout              " << endl;
	cout << "        -p: set precision, waf value less than it   " << endl;
	cout << "            will not be stored. if not set,         " << endl;
	cout << "            precision default to 0                  " << endl;
	cout << "        -t: set input term information file, term   " << endl;
	cout << "            with zero frequency will not calculate  " << endl;
	cout << "            waf value, other terms' frequencies will" << endl;
	cout << "            be used to calculate waf value.         " << endl;
	cout << "                                                    " << endl;
	return EXIT_SUCCESS;
}

template <typename InputIterator>
void init_option_table(
		InputIterator opt_beg, InputIterator opt_end,
		map<string,vector<string> >& option_table)
{
	option_table.clear();
	for (; opt_beg!=opt_end; ++opt_beg)
		option_table[*opt_beg].clear();
}

template <typename InputIterator>
void fill_option_table(
		InputIterator arg_beg, InputIterator arg_end,
		map<string,vector<string> >& option_table)
{
	while (arg_beg!=arg_end)
	{
		string arg=*arg_beg;
		if (option_table.count(arg))
		{
			vector<string>& v=option_table[arg];
			if (v.empty())
				v.push_back(arg);
			for (++arg_beg; arg_beg!=arg_end &&
					(arg=*arg_beg).substr(0,1)!="-"; ++arg_beg)
				v.push_back(arg);
		}
		else
		{
			option_table[""].push_back(arg);
			++arg_beg;
		}
	}
}

bool resolve_unknown_otions(
		const vector<string>& args, const string& func_hint)
{
	if (args.empty())
		return true;
	cerr << func_hint << "unknown option(s): ";
	copy(args.begin(),args.end(), ostream_iterator<string>(cerr," "));
	cerr << endl;
	return false;
}

bool resolve_file_input_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, istream*& is_ptr, ifstream& fin)
{
	bool exist=false;
	string file;
	if (!resolve_single_arg_option(args,opt_hint,func_hint,exist,file))
		return false;
	if (!exist)									//arg not exist, return true
		return true;
	fin.close(); fin.clear();
	fin.open(file.c_str(), ios::binary);
	if (!fin)
	{
		cerr << func_hint << "cannot open input file '"
			<< file << "'" << endl;
		return false;
	}
	is_ptr = &fin;
	return true;
}

bool resolve_files_input_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, int limit,
		bool& exist, vector<string>& file_vec)
{
	if (!resolve_multi_arg_option(args,opt_hint,func_hint,limit,exist,file_vec))
		return false;
	if (!exist)									//arg not exist, return true
		return true;
	for (vector<string>::size_type i=0; i<file_vec.size(); ++i)//check each file
	{
		ifstream fin(file_vec[i].c_str(),ios::binary);
		if (!fin)
		{
			cerr << func_hint << " cannot open input file '"
				<< file_vec[i] << "'" << endl;
			return false;
		}
	}
	return true;
}

bool resolve_file_output_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, ostream*& os_ptr, ofstream& fout)
{
	bool exist=false;
	string file;
	if (!resolve_single_arg_option(args,opt_hint,func_hint,exist,file))
		return false;
	if (!exist)									//arg not exist, return true
		return true;
	fout.close(); fout.clear();
	fout.open(file.c_str(),ios::binary);
	if (!fout)
	{
		cerr << func_hint << "cannot open output file '"
			<< file << "'" << endl;
		return false;
	}
	os_ptr = &fout;
	return true;
}

bool resolve_term_info_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, bool& exist, ifstream& fs)
{
	string file;
	if (!resolve_single_arg_option(args,opt_hint,func_hint,exist,file))
		return false;
	if (!exist)									//arg not exist, return true
		return true;
	fs.close(); fs.clear();
	fs.open(file.c_str(),ios::binary);
	if (!fs)
	{
		cerr << func_hint << "cannot open term info file '"
			<< file << "'" << endl;
		return false;
	}
	return true;
}

bool resolve_multi_arg_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, int limit,
		bool& exist, vector<string>& arg_vec)
{
	exist = true;
	switch (args.size())
	{
	case 0:
		exist = false;
		return true;
	case 1:
		cerr << func_hint << "missing argument for option " << opt_hint << endl;
		return false;
	default:									//one or more argument(s) specified
		if (args.size()-1>static_cast<vector<string>::size_type>(limit))//limit==-1 means no limit
		{
			cerr << func_hint << "too many arguments for option " << opt_hint << endl;
			return false;
		}
		for (vector<string>::size_type i=1; i<args.size(); ++i)
			arg_vec.push_back(args[i]);
		return true;
	}
}

bool resolve_single_arg_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, bool& exist, string& arg)
{
	vector<string> arg_vec;
	if(!resolve_multi_arg_option(args,opt_hint,func_hint,1,exist,arg_vec))
		return false;
	if (!exist)
		return true;
	arg = arg_vec[0];
	return true;
}

bool resolve_none_arg_option(
		const vector<string>& args, const string& opt_hint,
		const string& func_hint, bool& exist)
{
	vector<string> arg_vec;
	return resolve_multi_arg_option(args,opt_hint,func_hint,0,exist,arg_vec);
}

template <typename T, size_t N>
bool resolve_arg_in_table(
		const string& arg, const T (&table)[N],
		const string& opt_hint, const string& func_hint)
{
	if (find(table,table+N,arg)==table+N)
	{
		cerr << func_hint << "invalid argument for option " << opt_hint << endl;
		return false;
	}
	return true;
}

template <typename T1, typename T2>
ostream& operator << (ostream& os, const pair<T1,T2>& p)
{
	return os << "(" << p.first << "," << p.second << ")";
}

template <typename T1, typename T2>
istream& operator >> (istream& is, pair<T1,T2>& p)
{
	string text;
	getline(is,text,'(');
	getline(is,text,',');
	stringstream(text) >> p.first;
	getline(is,text,')');
	stringstream(text) >> p.second;
	return is;
}

template <typename T>
ostream& operator << (ostream& os, const node_info<T>& ni)
{
	return os << ni.row << "\t" << ni.col << "\t" << ni.value;
}

template <typename ToType, typename FromType>
ToType cast(const FromType& from)
{
	ToType to=ToType(); stringstream ss;
	ss << from; ss >> to; return to;
}

template <typename Iterator, typename OutputIterator>
void extract_node_info(Iterator first, Iterator last, OutputIterator out)
{
	for (; first!=last; ++first, ++out)
		*out = make_node_info(first.row_index(),first.col_index(),*first);
}

template <typename T>
void analyze_outlink(const cross_list<T>& mat,
		deque<deque<node_info<T> > >& node_vecs)
{
	typedef typename cross_list<T>::size_type size_type;
	node_vecs.resize(mat.row_size());
	{
		for (size_type r=0; r<mat.row_size(); ++r)
		{
			node_vecs[r].clear();				//sort nodes of each row by value desc
			extract_node_info(mat.cbegin_of_row(r),mat.cend_of_row(r),
					back_inserter(node_vecs[r]));
			sort(node_vecs[r].begin(),node_vecs[r].end(),
					node_info<T>::value_greater);
		}
	}
}


template <typename T>
void analyze_inlink(const cross_list<T>& mat,
		deque<deque<node_info<T> > >& node_vecs)
{
	typedef typename cross_list<T>::size_type size_type;
	node_vecs.resize(mat.col_size());
	{
		for (size_type c=0; c<mat.col_size(); ++c)
		{
			node_vecs[c].clear();				//sort nodes of each column by value desc
			extract_node_info(mat.cbegin_of_col(c),mat.cend_of_col(c),
					back_inserter(node_vecs[c]));
			sort(node_vecs[c].begin(),node_vecs[c].end(),
					node_info<T>::value_greater);
		}
	}
}


template <typename T>
void analyze_pair(const cross_list<T>& mat,
		deque<node_info<T> >& node_vec)
{
	node_vec.clear();
	extract_node_info(mat.cbegin(),mat.cend(),back_inserter(node_vec));
	sort(node_vec.begin(),node_vec.end(),
			node_info<T>::value_greater);		//sort nodes by value desc
}

template <typename T>
bool analyze_matrix(const cross_list<T>& mat, const string& type,
		const string& func_hint, waf::size_type node_limit,
		deque<deque<node_info<T> > >& node_vecs)
{
	typedef deque<deque<node_info<T> > > node_vecs_type;
	node_vecs.resize(1);
	if ("pair"==type)
		analyze_pair(mat,node_vecs.front());
	else if ("in-link"==type)
		analyze_inlink(mat,node_vecs);
	else if ("out-link"==type)
		analyze_outlink(mat,node_vecs);
	else
	{
		cerr << func_hint << "invalid analyze type '" << type << "'" << endl;
		return false;
	}

	//match node limit size
	for (typename node_vecs_type::iterator iter=node_vecs.begin();
			iter!=node_vecs.end(); ++iter)
	{
		if (iter->size()>node_limit)
			iter->resize(node_limit);
	}
	return true;
}

template <typename T, typename Term>
bool analyze_process(
		istream& is, bool use_term_info,term_info<Term>& tinfo,
		const string& analyze_type, const string& func_hint,
		waf::size_type node_limit, ostream& os)
{
	cross_list<T> mat;							//read matrix
	is >> mat;

	if (use_term_info)							//use term info to cut unconcerned nodes
	{
		waf::termid_type termid_limit=
			min(tinfo.termid_size(),min(mat.row_size(),mat.col_size()));
		for (waf::termid_type termid=0; termid<termid_limit; ++termid)
		{
			if (tinfo.freq_of_id(termid)>0)
				continue;
			mat.erase(mat.begin_of_row(termid),mat.end_of_row(termid));
			mat.erase(mat.begin_of_col(termid),mat.end_of_col(termid));
		}
	}

	typedef deque<deque<node_info<T> > > node_vecs_type;
	node_vecs_type node_vecs;					//analyze matrix nodes
	if (!analyze_matrix(mat,analyze_type,func_hint,node_limit,node_vecs))
		return EXIT_FAILURE;

	if (use_term_info)							//output info to output stream
	{
		os << "<from_term>\t<to_term>\t<value>" << endl;
		for (typename node_vecs_type::iterator iter=node_vecs.begin();
				iter!=node_vecs.end(); ++iter)
		{
			for (typename deque<node_info<T> >::iterator
					it=iter->begin(); it!=iter->end(); ++it)
			{
				os << tinfo.term_of_id(it->row) << "\t"
					<< tinfo.term_of_id(it->col) << "\t"
					<< it->value << endl;
			}
		}
	}
	else
	{
		os << "<from_termid>\t<to_termid>\t<value>" << endl;
		for (typename node_vecs_type::iterator iter=node_vecs.begin();
				iter!=node_vecs.end(); ++iter)
		{
			copy(iter->begin(),iter->end(),
					ostream_iterator<node_info<T> >(os,"\n"));
		}
	}
	return EXIT_SUCCESS;
}
